package com.piesat.quake.business.device.mqtt.pipeline;

import com.piesat.quake.business.device.mqtt.LengthData;
import com.piesat.quake.business.device.mqtt.pipeline.datahandler.DataHandler;
import com.piesat.quake.business.device.mqtt.pipeline.datahandler.DefaultDataHandler;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

/**
 * 数据解析流水线
 *
 * @author Gersy
 * @date 2023/3/2
 * @since 1.0
 */
public class Pipeline implements DataPipeline {

    private final LinkedList<DataHandler> handlers = new LinkedList<>();
    private final Map<String, DataHandler> handlerMap = new HashMap<>();
    private final Class<?> clazz;

    public Pipeline(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public DataHandler getLast() {
        return handlers.getLast();
    }

    @Override
    public DataPipeline addFirst(DataHandler handler) {
        handlers.add(0, handler);
        return this;
    }

    @Override
    public DataPipeline addLast(DataHandler handler) {
        handlers.add(handler);
        return this;
    }

    @Override
    public DataPipeline addLast(Class<?> clazz) {
        DefaultDataHandler dataHandler = new DefaultDataHandler(clazz);
        handlers.add(dataHandler);
        return this;
    }

    @Override
    public DataPipeline addNode(Class<?> clazz, String nodeFieldName) {
        return addNode(new DefaultDataHandler(clazz), nodeFieldName);
    }

    @Override
    public DataPipeline addNode(Class<?> clazz, String nodeFieldName, String[] excludeFields) {
        return addNode(new DefaultDataHandler(clazz, excludeFields), nodeFieldName);
    }

    @Override
    public DataPipeline addNode(Class<?> clazz, String parentNode, String nodeFieldName, String[] excludeFields) {
        return addNode(new DefaultDataHandler(clazz, excludeFields), parentNode, nodeFieldName);
    }

    @Override
    public DataPipeline addNode(Class<?> clazz, String parentNode, String nodeFieldName) {
        return addNode(new DefaultDataHandler(clazz), parentNode, nodeFieldName);
    }

    @Override
    public DataPipeline addNode(Class<?> clazz, String parentNode, String nodeFieldName, int size) {
        return addNode(new DefaultDataHandler(clazz), parentNode, nodeFieldName, size);
    }

    @Override
    public DataPipeline addNode(DataHandler handler, String nodeFieldName) {
        handlers.add(handler);
        handler.setNodeFieldName(nodeFieldName);
        handlerMap.put(nodeFieldName, handler);
        return this;
    }

    @Override
    public DataPipeline addNode(DataHandler handler, String parentNode, String nodeFieldName) {
        return addNode(handler, parentNode, nodeFieldName, 1);
    }

    @Override
    public DataPipeline addNode(DataHandler handler, String parentNode, String nodeFieldName, int size) {
        DataHandler dataHandler = handlerMap.get(parentNode);
        if (dataHandler == null) {
            throw new IllegalArgumentException("没有找到匹配的父级节点：" + parentNode);
        }
        for (int i = 0; i < size; i++) {
            dataHandler.addChild(handler);
        }
        handler.setNodeFieldName(nodeFieldName);
        handlerMap.put(nodeFieldName, handler);
        return this;
    }

    public DataPipeline addLastBatch(DataHandler handler, int number) {
        for (int i = 0; i < number; i++) {
            handlers.add(handler);
        }
        return this;
    }

    @Override
    public LengthData decode(byte[] bytes) {
        try {
            LengthData data = (LengthData) clazz.getConstructor().newInstance();
            ByteBuf byteBuf = Unpooled.copiedBuffer(bytes);
            recurrenceDecode(handlers, data, byteBuf);
            return data;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void recurrenceDecode(List<DataHandler> handlers, Object currentData, ByteBuf byteBuf) throws NoSuchFieldException, IllegalAccessException {
        for (DataHandler handler : handlers) {
            String nodeFieldName = handler.getNodeFieldName();
            if (nodeFieldName != null) {
                Class<?> aClass = currentData.getClass();
                Field field = aClass.getDeclaredField(nodeFieldName);
                field.setAccessible(true);
                Object decode = handler.decode(byteBuf, null);
                field.set(currentData, decode);
                if (handler.getChildren() != null) {
                    recurrenceDecode(handler.getChildren(), decode, byteBuf);
                }
            } else {
                handler.decode(byteBuf, currentData);
            }
        }
    }

    @Override
    public byte[] encode(LengthData data) {
        try {
            int length = this.getLength();
            data.setLength(length);
            ByteBuf byteBuf = Unpooled.buffer(length);
            recurrenceEncode(handlers, data, byteBuf);
            return byteBuf.array();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void recurrenceEncode(List<DataHandler> handlers, Object currentData, ByteBuf byteBuf) throws NoSuchFieldException, IllegalAccessException {
        for (DataHandler handler : handlers) {
            String nodeFieldName = handler.getNodeFieldName();
            if (nodeFieldName != null) {
                Class<?> aClass = currentData.getClass();
                Field field = aClass.getDeclaredField(nodeFieldName);
                field.setAccessible(true);
                Object data = field.get(currentData);
                if (data == null) {
                    continue;
                }
                handler.encode(byteBuf, data);
                if (handler.getChildren() != null) {
                    recurrenceEncode(handler.getChildren(), data, byteBuf);
                }
            } else {
                handler.encode(byteBuf, currentData);
            }
        }
    }

    @Override
    public int getLength() {
        int length = 0;
        for (DataHandler handler : handlers) {
            length += handler.getLength();
        }
        return length;
    }


}
